package state

import (
	"osiris/core/conversion"
	"osiris/dao"
	"osiris/dto"
	"osiris/errors"
	"osiris/logger"
	"osiris/mpt"
	"sync"
	"time"

	peer "github.com/libp2p/go-libp2p/core/peer"
)

// PeerStateTrie 节点状态树(key: peer.ID对应的字节数组，value: dto.StateLeaf)
type PeerStateTrie struct {
	BaseStateTrie

	buf map[peer.ID]dto.StateLeaf
}

func (trie *PeerStateTrie) InitState() error {
	trie.stateMutex = new(sync.RWMutex)
	trie.stateTrie = mpt.NewTrie()
	trie.buf = make(map[peer.ID]dto.StateLeaf, 0)

	pubKeyModels, err := dao.GetAllPubKeys()
	if err != nil {
		logger.Error(map[string]interface{}{"[state] [Init Peer Trie] dao.GetAllPubKeys()": err})
		return err
	}

	var errOutside error
	for _, pubkeyModel := range pubKeyModels {
		peerIDBytes, err1 := conversion.StrToPeerIDBytes(pubkeyModel.PeerIDStr)
		if err1 != nil {
			errOutside = err1
			logger.Error(map[string]interface{}{"[state] [Init Peer Trie] peer.ID.String() to bytes": err1})
			continue
		}

		leaf := dto.StateLeaf{
			Asset:     0,
			CoolDown:  false,
			CoinSince: 0,
		}
		trie.commitState(peerIDBytes, leaf, false)

		peerID, err2 := conversion.StrToPeerID(pubkeyModel.PeerIDStr)
		if err2 != nil {
			logger.Error(map[string]interface{}{"[state] [Init Peer Trie] from peerIDStr to peer.ID": err2})
			return err2
		}
		trie.buf[peerID] = leaf
	}

	return errOutside
}

func (trie *PeerStateTrie) ResetState() {
	trie.stateMutex.Lock()
	trie.stateTrie = mpt.NewTrie()
	trie.buf = make(map[peer.ID]dto.StateLeaf, 0)
	trie.stateMutex.Unlock()
}

func (trie *PeerStateTrie) commitState(key []byte, leaf dto.StateLeaf, writeDB bool) error {
	leafBytes, err1 := dto.ToBytes(leaf)
	if err1 != nil {
		logger.Error(map[string]interface{}{"[state] [commit Peer State] from StateLeaf to bytes": err1})
		return err1
	}

	trie.stateTrie.Put(key, leafBytes)
	peerID, err2 := conversion.BytesToPeerID(key)
	if err2 != nil {
		logger.Error(map[string]interface{}{"[state] [commit Peer State] from StateLeaf to bytes": err2})
		return err2
	}

	trie.buf[peerID] = leaf
	return nil
}

func (trie *PeerStateTrie) coolDownPeer(peerIDBytes []byte) error {
	leaf, exist := trie.GetLeaf(peerIDBytes)
	if !exist {
		err := &errors.PeerNotExistError{}
		logger.Error(map[string]interface{}{"[state] [cool Down Peer] get StateLeaf": err})
		return err
	}
	coolDownLeaf := leaf
	coolDownLeaf.CoolDown = true

	trie.stateMutex.Lock()
	defer trie.stateMutex.Unlock()
	return trie.commitState(peerIDBytes, coolDownLeaf, false)
}

func (trie *PeerStateTrie) warmUpPeers() error{
	trie.stateMutex.Lock()
	defer trie.stateMutex.Unlock()

	var errOutside error
	for peerID, leaf := range trie.buf {
		if leaf.Asset == 0 && leaf.CoolDown {
			peerIDBytes, err := peerID.MarshalBinary()
			if err != nil {
				logger.Error(map[string]interface{}{"[state] [trie.warmUpPeers()] peerID.MarshalBinary()": err})
				errOutside=err
				continue
			}

			warmUpLeaf := leaf
			warmUpLeaf.CoolDown = false
			trie.commitState(peerIDBytes, warmUpLeaf, false)
		}
	}

	return errOutside
}

// getHeightStake 当前拥有最多stake的节点(如果多个节点的stake相同，会选第一个查到的节点)
//
//	@receiver trie
//	@return peer.ID
//	@return error
func (trie *PeerStateTrie) getHighestStake() (peer.ID, error) {
	trie.Lock()
	defer trie.UnLock()

	if len(trie.buf) == 0 {
		return "", &errors.NoAvailablePeerError{
			Msg: "the length of trie.rank is 0",
		}
	}

	var highestPeerID peer.ID
	var highestStake int64 = -1
	currentTS := time.Now().Unix()
	for peerID, leaf := range trie.buf {
		if leaf.CoolDown {
			continue
		}

		tempStake := leaf.Asset * (currentTS - leaf.CoinSince)
		if tempStake > highestStake {
			highestPeerID = peerID
			highestStake = tempStake
		}
	}

	if highestStake == -1 {
		return "", &errors.NoAvailablePeerError{
			Msg: "all peer is cooled down",
		}
	}

	return highestPeerID, nil
}

func (trie *PeerStateTrie) IsTxOutOfDate(txType dto.TxType, addrStr string, nonce int64) bool {
	//TODO 时间戳校验？比上一个区块产生的时间戳还早就不算？
	return true
}
